#!/usr/bin/env ruby
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# This script parses the Mame sources to establish le list of ROM archives recognised by Mame.
# Is uses this list to validate a set of existing ROM archives. The validity of the ROMs is
# checked using the SHA1 defined in Mame sources. If the Zip archive does not have the
# corresponding rom of SHA1 sum we try to repair by renaming the roms, or getting a rom with the
# same sha1 from another archive.
# 
# TODO: Need to handle the ROMs using BIOS dependencies.
# TODO: Need to handle some tricky include directives for some ROM definition (ex sfiii3)
# TODO: a lot of cleanup code
#

# Place where the actual ROMs are placed
rom_path = "/MyRoms"

# Place where the new ROMs will be placed
new_rom_path = "/MyNewRoms"

# Place where the Mame sources are places
mame_sources_path = "/Sources/mame0143s"

`mkdir -p #{new_rom_path}`

class Rom
 
 def initialize(name, sha1, archive)
  @name = name
  @sha1 = sha1
  @archive = archive
 end
 
 def name
  @name
 end
 
 def sha1
  @sha1
 end
 
 def archive
  @archive
 end
 
 def ==(other)
  if other.kind_of? Rom
    name == other.name && sha1 == other.sha1
  else
    super(other)
  end
 end
 
 def to_s
   "#{name}:#{sha1}"
 end
 
 def description
   @archive.ArchiveDescription
 end
 
end

class RomArchive
 
 def initialize(name)
   @name = name
   @roms = {}
   @sha1s = {}
 end
 
 def name
  @name
 end
 
 def ArchiveDescription
   "RomArchive #{@name}"
 end
 
 def addRom(name, sha1)
  if @roms.has_key?(name)
   if @roms[name] == sha1
    puts "WARN #{self.ArchiveDescription}: #{name} already defined\n"
    return
   else
    puts "WARN #{self.ArchiveDescription}: #{name} defined twice with #{sha1} and #{@roms[name]}\n"
    return
   end
  end
  if @sha1s.has_key?(sha1)
    if @sha1s[sha1] != name
      puts "WARN #{self.ArchiveDescription}: #{sha1} used by #{name} and #{@sha1s[sha1]}\n"
    end
  end
  @roms[name] = sha1
  @sha1s[sha1] = name
 end
 
 def roms
  @roms
 end
 
 def sha1s
  @sha1s
 end
 
 def ==(other)
  if other.kind_of? RomArchive
    @roms == other.roms
  else
    super(other)
  end
 end
 
 def to_s
  result = "#{@name}: #{self.ArchiveDescription}\n"
  @roms.each_pair { | key, value |
   result += "\t#{key}:#{value}\n"
  }
  result
 end
 
end

class MameRom < RomArchive
 @@allMameRoms = {}
 
 def initialize(name, file)
  super(name)
  @file = file
 end
 
 def ArchiveDescription
   "MameRom #{@name} #{@file}"
 end
 
 def MameRom.ProcessFile(fileName)
  currentRom = nil
  startMatch = /ROM_START\([ \t]*([^ \t)]+)/
  loadMatch = /[A-Z0-9_]+\([ \t]*"([^"]+)",[ \t]*0x[0-9a-fA-F]+,[ \t]*0x[0-9a-fA-F]+,[ \t]*CRC\(([0-9a-fA-F]{8})\)[ \t]*SHA1\(([0-9a-fA-F]{40})/
  endMatch = /ROM_END/
  File.open(fileName).each { |line|
   if ((match = startMatch.match(line)) != nil)
    if (currentRom != nil)
      puts "WARN: got #{line} before ending of #{currentRom.name} in #{fileName}\n"
    end
    currentRom = MameRom.new(match[1], fileName)
    if (@@allMameRoms.has_key?(match[1]))
      puts "WARN: two Roms with same name: #{currentRom.ArchiveDescription} and #{@@allMameRoms[match[1]].ArchiveDescription}\n"
    end
    @@allMameRoms[match[1]] = currentRom
    next
   end
  
   if ((match = loadMatch.match(line)) != nil)
    if (currentRom == nil)
       puts "WARNING: got #{line} before START in #{fileName}\n"
    else
       currentRom.addRom(match[1], match[3])
    end
    next
   end
  
   if ((match = endMatch.match(line)) != nil)
    if (currentRom == nil)
      puts "WARNING: got #{line} before START in #{fileName}\n"
    end
    currentRom = nil
    next
   end
  }
 end
 
 def MameRom.Display
  @@allMameRoms.each_value { |r|
   puts r.to_s
  }
 end
 
 def MameRom.allRoms
  @@allMameRoms
 end
 
end


class ZipRom < RomArchive

 @@allZipRomsBySHA1 = {} 

 def initialize(fileName)
   super(File.basename(fileName, ".zip"))
   @fileName = fileName
   `unzip -l #{fileName}`.each_line { |l|
	match = /[0-9]+[ \t]+..-..-.. ..:..[ \t]+([^ \t\r\n]+)/.match(l)
	if (match != nil)
	  romName = match[1]
	  sha1 = `unzip -p #{fileName} #{romName} | shasum | cut -f 1 -d " "`.chomp
	  addRom(romName, sha1)
	end
   }
 end

 def ArchiveDescription
   "ZipRom #{@name} #{@fileName}"
 end
 
 def fileName
   @fileName
 end
 
 def addRom(romName, sha1)
  rom = Rom.new(romName, sha1, self)
  if (!@@allZipRomsBySHA1.has_key?(sha1))
    @@allZipRomsBySHA1[sha1] = []
  end
  @@allZipRomsBySHA1[sha1] += [rom]
  
  super(romName, sha1)
 end

 def ZipRom.find_all(sha1)
   @@allZipRomsBySHA1[sha1]
 end
 
end


def RecursFindFiles(path)
 Dir.foreach(path) { |x|
  fullPath = File.join(path, x)
  if (FileTest.directory?(fullPath))
   if (x != "." &&
       x != "..")
    RecursFindFiles(fullPath) { |x| yield x }
   end
  else
    yield fullPath
  end
 }
end


RecursFindFiles(mame_sources_path) { |x|
  if (x =~ /\.c$/)
    MameRom.ProcessFile(x)
  end
}

puts "Mame Rom List:\n"
MameRom.Display

@@allZips = {}

RecursFindFiles(rom_path) { |x|
   if (x =~ /\.zip$/)
     zipRom = ZipRom.new(x)
     @@allZips[zipRom.name] = zipRom
   end
}

puts "Zip Rom List:\n"
@@allZips.each_value { |r|
 puts r.to_s
}


@@allZips.each_value { |zipRom|
 if MameRom.allRoms.has_key?(zipRom.name)
   romIsValid = true
   mameRom = MameRom.allRoms[zipRom.name]
   
   if mameRom == zipRom
     puts "#{zipRom.ArchiveDescription} is OK\n"
     next
   end
   
   #
   # ZipRom is invalid, check if it corresponds to another Rom
   replacements = MameRom.allRoms.values.find_all { |rom| rom == zipRom }
   if replacements.length > 0
     puts "#{zipRom.ArchiveDescription} is invalid, but corresponds to #{replacements.join(", ")}\n"
     `cp #{zipRom.fileName} #{new_rom_path}/#{MameRom.name}.zip`
     next
   end
   
   #
   # No perfect replacement found, try to rebuild the game from all available Roms
   
   puts "#{zipRom.ArchiveDescription} is not the same as the one from Mame.\n"
   listForRoms = []
   missingRoms = []
   
   mameRom.roms.each_pair { |romName, sha1|
     if (zipRom.sha1s.has_key?(sha1))
       puts "\tuse #{zipRom.sha1s[sha1]} as #{romName}\n"
       listForRoms += [ "unzip -p #{zipRom.fileName} #{zipRom.sha1s[sha1]} > #{new_rom_path}/#{mameRom.name}/#{romName}" ]
       next
     end
     
     replacements = ZipRom.find_all(sha1)
     if replacements != nil && replacements.length > 0
       puts "\treplacements for #{romName}:\n"
       replacements.each { |rom|
         puts "\t\t#{rom.name} from #{rom.description}\n"
       }
       #use first
       listForRoms += [ "unzip -p #{replacements[0].archive.fileName} #{replacements[0].name} > #{new_rom_path}/#{mameRom.name}/#{romName}" ]
       next
     end
     
     missingRoms += ["#{romName} : #{sha1}"]
   }
   
   if missingRoms.length > 0
    puts "\tShopping list:" + missingRoms.join("\n\tShoping list:")
   else
    puts "\tre-building #{mameRom.name}..."
    `mkdir #{new_rom_path}/#{mameRom.name}`
    listForRoms.each { |cmd|
       if cmd =~ /sfix/
         next # skip neo-geo bios
       end
       puts "#{cmd}\n"
      `#{cmd}`
    }
    puts "cd #{new_rom_path}/#{mameRom.name}; zip ../#{mameRom.name}.zip *\n"
    `cd #{new_rom_path}/#{mameRom.name}; zip ../#{mameRom.name}.zip *`
    puts "rm -rf #{new_rom_path}/#{mameRom.name}"
    `rm -rf #{new_rom_path}/#{mameRom.name}`
    puts "Done\n"
   end
 else
 
   #
   # ZipRom is invalid, check if it corresponds to another Rom
   replacements = MameRom.allRoms.values.find_all { |rom| rom == zipRom }
   if replacements.length > 0
     puts "#{zipRom.ArchiveDescription} is unkown by mame, but corresponds to #{replacements.join(", ")}\n"
     next
   end
   puts "Rom #{zipRom.ArchiveDescription} is unknown to Mame.\n"
 end
}
